Creating Custom Components: Credit Card Box

From Documentation
DocumentationSmall Talks2024OctoberCreating Custom Components: Credit Card Box
Creating Custom Components: Credit Card Box

Author
Hawk Chen, Manager, Potix Corporation
Date
Oct. 21, 2024
Version
ZK 9 or later

Overview

In this article, I will guide you on how to create a custom ZK component by combining existing ZK components. Moreover, we'll apply a customized ZK JavaScript widget to enhance client-side behavior.

We will start by demonstrating the process with a single ZK Textbox and then enhance it with a more advanced solution using multiple Textboxes. The smart credit card input field will be customized to accept only numeric entries and will also group the digits into four-digit sets, making it easier to read and verify.

Build with one Textbox

The most straightforward approach is to use a single Textbox for the credit card input and set its `maxlength` and `col` to 16.

<textbox type="tel" cols="16" maxlength="16"/>

However, the downside is that all the numbers appear "crowded together," which makes them difficult to read.

Customize a Number Padding Box

To improve readability, we can customize the textbox widget to insert a space character after every four digits. This customization will also ensure that only numbers are entered.

Creditcardbox-1-textbox.gif

Here's where you would insert the required widget code in TypeScript:

@zk.WrapClass('business.input.NumberPaddingBox')
// @ts-ignore
class NumberPaddingBox extends zul.inp.Textbox {
    /** default onInput event listener **/
    doInput_(event: zk.Event) {
        super.doInput_(event);
        this.removeNonNumericCharacters();
        this.addPaddingEach4Digits();
    }

    removeNonNumericCharacters() {
        // @ts-ignore
        this.getInputNode().value = this.getInputNode().value.replace(/\D/g, '');
    }

    addPaddingEach4Digits() {
        // @ts-ignore
        let value = this.getInputNode().value;
        // avoid exceeding credit card number length when pasting texts
        value = value.substring(0, 16);
        // @ts-ignore
        this.getInputNode().value = value.replace(/(\d{4})(?=\d)/g, '$1 ');
    }

    getZclass() {
        return 'z-textbox';
    }
}

To apply this customized JavaScript widget to a textbox:

<textbox type="tel" cols="19" maxlength="19"  w:use="business.input.NumberPaddingBox"/>

Declare as ZK Component

For ease of use, it's beneficial to declare this setup as a ZK component. This way, developers can use it like a regular component without specifying a custom widget name. Additionally, you can set default attributes for the credit card input.

An example declaration in lang-addon.xml:

<language-addon>
    <addon-name>business-input</addon-name>
    <depends>zul</depends>
    <language-name>xul/html</language-name>
    <version>
        <zk-version>10.0.0</zk-version>
    </version>
    ...
    <component>
        <component-name>creditcardbox</component-name>
        <extends>textbox</extends>
        <widget-class>business.input.NumberPaddingBox</widget-class>
        <property>
            <property-name>type</property-name>
            <property-value>tel</property-value>
        </property>
        <property>
            <property-name>cols</property-name>
            <property-value>19</property-value>
        </property>
        <property>
            <property-name>maxlength</property-name>
            <property-value>19</property-value>
        </property>
    </component>

Check lang-addon.xml specification.

Build with Four Textboxes

If you prefer a more structured input, you can use four separate textboxes to divide the 16 digits, ensuring easier entry and organization.

<zk xmlns:w="client" xmlns:ca="client/attribute">
    <textbox type="tel" ca:inputmode="numeric" cols="4" maxlength="4" forEach="1,2,3,4"/>
</zk>

Combine the entries in 4 Textboxes with a Macro

Typically, when using four separate textboxes for credit card input, you would need to retrieve and combine the entries from each box. To simplify this process, you can create a macro component that automatically consolidates the inputs into a single value.

4-textbox-macro.png

Retrieving the credit card number can then be done using:

getNumber()

Here's where to insert the Java code for the macro component:

public class Creditcardbox4 extends HtmlMacroComponent {
    public static final String CSS_CLASS = "z-creditcardbox4";

    public Creditcardbox4() {
        this.setMacroURI("~./js/business/input/zul/creditcardbox4.zul");
        this.setZclass(CSS_CLASS);
    }

    @Wire("textbox")
    protected List<Textbox> textboxList;
    /**
     * @return credit card number
     */
    public String getNumber(){
        String number = "";
        for (Textbox textbox : textboxList) {
            number += textbox.getValue();
        }
        return number;
    }
}

Customize a Focus Jumping Box

Manually moving the focus among four textboxes while entering digits can be cumbersome. To enhance usability, I developed a JavaScript widget with the following features:

  • Automatically shifts focus to the next textbox when typing reaches the maximum length.
  • Moves focus to the previous textbox when pressing backspace in an empty textbox.
Creditcardbox-4-textbox.gif
@zk.WrapClass('business.input.FocusJumpingBox')
// @ts-ignore
class FocusJumpingBox extends zul.inp.Textbox {
    /** default onInput event listener **/
    doInput_(event: zk.Event) {
        super.doInput_(event);
        this.removeNonNumericCharacters();
        this.focusNextBoxIfComplete();
    }

    ...

    focusNextBoxIfComplete() {
        // @ts-ignore
        if (this.getInputNode().value.length == this.getMaxlength()
            && !this.isLastBox()) {
            this.nextSibling?.focus();
        }
    }

    /** default onKeyDown event listener **/
    doKeyDown_(event: zk.Event) {
        super.doKeyDown_(event);
        this.focusPreviousBoxOnBackspace(event);
    }

    focusPreviousBoxOnBackspace(event: zk.Event) {
        // @ts-ignore
        if (this.getInputNode().value.length == 0
            && !this.isFirstBox()
            && event.key == 'Backspace') {
            this.previousSibling?.focus();
        }
    }

    isLastBox() {
        return this.nextSibling == null;
    }

    isFirstBox() {
        return this.previousSibling == null;
    }

    getZclass() {
        return 'z-textbox';
    }
}

Declare as ZK Component

Due to the component's Java and JavaScript portions, declaring it as a ZK component is advisable for ease of use.

Refer to the following example in lang-addon.xml:

<language-addon>
    <addon-name>business-input</addon-name>
    <depends>zul</depends>
    <language-name>xul/html</language-name>
    <version>
        <zk-version>10.0.0</zk-version>
    </version>
    <component>
        <component-name>creditcardbox4</component-name>
        <component-class>org.zkoss.business.Creditcardbox4</component-class>
    </component>

Try it In Your Project

Using ZK's custom component mechanism, you can create a tailored CreditCardBox component with unique server and client features to enhance the user experience. I hope this has demonstrated how you can customize and extend existing ZK components to meet your needs.

Note that while the methods and ideas mentioned in this article apply to both ZK 9 and ZK 10, the resulting component supports only ZK 10 and later. To try out the Beta version of the component, include the latest version in your Maven pom file from zk repository.

        <dependency>
            <groupId>org.zkoss.addon</groupId>
            <artifactId>business-input</artifactId>
            <version>${version}</version>
        </dependency>

Then use it in a zul like:

<creditcardbox/>
<creditcardbox4/>

Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.